// Default stream and parsers for Uint8TypedArray data type

export const buildStream = (uint8Data) => ({
    data: uint8Data,
    pos: 0,
})

export const readByte = () => (stream) => {
    return Promise.resolve(stream.data[stream.pos++])
}

export const peekByte =
    (offset = 0) =>
    (stream) => {
        return Promise.resolve(stream.data[stream.pos + offset])
    }

export const readBytes = (length) => (stream) => {
    return Promise.resolve(
        stream.data.subarray(stream.pos, (stream.pos += length))
    )
}

export const peekBytes = (length) => (stream) => {
    return Promise.resolve(stream.data.subarray(stream.pos, stream.pos + length))
}

export const readString = (length) => (stream) => {
    return readBytes(length)(stream).then((bytes) =>
        Array.from(bytes)
        .map((value) => String.fromCharCode(value))
        .join('')
    )
}

export const readUnsigned = (littleEndian) => (stream) => {
    return readBytes(2)(stream).then((bytes) => {
        return littleEndian ?
            (bytes[1] << 8) + bytes[0] :
            (bytes[0] << 8) + bytes[1]
    })
}

export const readArray =
    (byteSize, totalOrFunc) => (stream, result, parent) => {
        const totalPromise =
            typeof totalOrFunc === 'function' ?
            totalOrFunc(stream, result, parent) :
            totalOrFunc
        return Promise.resolve(totalPromise).then((total) => {
            const parser = readBytes(byteSize)
            const arr = new Array(total)
            let promiseLoop = Promise.resolve()
            for (var i = 0; i < total; i++) {
                promiseLoop = promiseLoop.then((e) => parser(stream))
                arr[i] = promiseLoop
            }

            return promiseLoop.then((e) => Promise.all(arr))
        })
    }

const subBitsTotal = (bits, startIndex, length) => {
    var result = 0
    for (var i = 0; i < length; i++) {
        result += bits[startIndex + i] && 2 ** (length - i - 1)
    }
    return result
}

export const readBits = (schema) => (stream) => {
    return readByte()(stream).then((byte) => {
        // convert the byte to bit array
        const bits = new Array(8)
        for (var i = 0; i < 8; i++) {
            bits[7 - i] = !!(byte & (1 << i))
        }
        // convert the bit array to values based on the schema
        return Object.keys(schema).reduce((res, key) => {
            const def = schema[key]
            if (def.length) {
                res[key] = subBitsTotal(bits, def.index, def.length)
            } else {
                res[key] = bits[def.index]
            }
            return res
        }, {})
    })
}